#region Header File
//
// Port.cs - Graph's Port
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
#endregion


namespace Dff
{
    public enum PortType { I, O };

    /// <summary>
    /// Graph's Port. Thought this object you can send and receive samples of type T to your 
    /// neigbourhood in the graph. One port can be connected to many *diferent* edges.
    /// </summary>    
    /// <typeparam name="T">Data type</typeparam>
    public class Port<T>
    {
        Edge<T>        edge;          // connected Edge
        Dispatcher     disp;          // reference to a Node's Dispatcher object
        AutoResetEvent producerWait;  // wait handle for producer (enqueue)
        AutoResetEvent consumerWait;  // wait handle for consumer (dequeue)
        int            producerReq;   // amount of data requested by producer
        int            consumerReq;   // amount of data requested by consumer

        object         onlyOne = new object();

        #region Auto-implemented Properties
        public int      Index       { get; private set; }   // index for this port, in case this port
        public string   Name        { get; set;         }   // port name
        public string   Description { get; set;         }   // port description 
        public PortType Type        { get; private set; }   // port type 
        public bool     Connected   { get; private set; }   // is there an edge connected?
        public int      Threshold   { get; set;         }   // threshold of packets to raise an event
        #endregion

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="type">Port type</param>
        /// <param name="index">Port number</param>
        public Port(PortType type, int index)
            : this(type, index, null)
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="type">I or O</param>
        /// <param name="index">Number associated to this port</param>
        /// <param name="dispatcher">Dispatcher</param>
        public Port(PortType type, int index, Dispatcher dispatcher)
        {
            this.disp      = dispatcher; 
            this.Type      = type;
            this.Index     = index;
            this.Threshold = 1;
            this.Connected = false;

            producerWait = new AutoResetEvent(false);
            consumerWait = new AutoResetEvent(false);
        }
        #endregion

        #region Events
        public event DispatcherDelegate PortAttach;
        public event DispatcherDelegate PortDetach;
        public event DispatcherDelegate EdgeChange;
        #endregion

        #region Internal Methods
        /// <summary>
        /// Called by Edge when the port is connected to an edge
        /// </summary> 
        internal void OnPortAttach(Edge<T> e)
        {
            edge      = e;
            Connected = true;

            if (PortAttach == null)
            {
                return;
            }

            if (disp != null)
            {
                disp.BeginInvoke(PortAttach, Index);
            }
            else
            {
                PortAttach(Index);
            }
        }

        /// <summary>
        /// Called by Edge when the port is disconnected
        /// </summary> 
        internal void OnPortDetach()
        {
            edge = null;
            Connected = false;

            if (PortDetach == null)
            {
                return;
            }
            
            if (disp != null)
            {
                disp.BeginInvoke(PortDetach, Index);
            }
            else
            {
                PortDetach(Index);
            }
        }

        /// <summary>
        /// Called by Edge when new data on the queue is available.
        /// </summary> 
        internal void OnEdgeChangeForIPort(int count)
        {
            // release blocked producer (if any)
            if (consumerReq >= count)
                consumerWait.Set();
            
            if (EdgeChange != null)
                disp.BeginInvoke(EdgeChange, Index);
        }

        /// <summary>
        /// Called by Edge when free space on the queue is available.
        /// </summary>
        internal void OnEdgeChangeForOPort(int space)
        {
            // release blocked producer (if any)
            if (producerReq <= space)
                producerWait.Set();

            if (EdgeChange != null)
                disp.BeginInvoke(EdgeChange, Index);                
        }
        #endregion     

        /// <summary>
        /// Transfer data function, it performs the Enqueue() operation on connected edge 
        /// </summary>
        /// <param name="item">Item to be enqueued</param>
        /// <returns></returns>
        public int Enqueue(T token)
        {           
            Debug.Assert(Type == PortType.O);
            lock (onlyOne)
            {
                producerReq = 1;
                // only one thread can sleep on producerwait
                if (edge.Space == 0)
                    producerWait.WaitOne();

                edge.Enqueue(token);
            }

            return 1;
        }

        /// <summary>
        /// Transfer data function, it performs the Enqueue() operation on connected edge 
        /// </summary>
        /// <param name="buffer">Buffer data</param>
        /// <param name="offset">Zero-based index offset</param>
        /// <param name="count">Number of tokens to be transfered</param>
        /// <returns>Number of tokens actually transfered</returns>
        public int Enqueue(T[] buffer, int offset, int count)
        {
            Debug.Assert(Type == PortType.O);
            lock (onlyOne)
            {
                producerReq = count;
                if (edge.Space == 0)
                    producerWait.WaitOne();

                edge.Enqueue(buffer, offset, count);
            }

            return count;
        }

        /// <summary>
        /// Transfer data function, it performs the Dequeue() operation on connected edge 
        /// </summary>
        /// <param name="item">Item to be dequeued</param>
        /// <returns></returns>
        public int Dequeue(out T token)
        {
            Debug.Assert(Type == PortType.I);            
            lock (onlyOne)
            {
                consumerReq = 1;
                if (edge.Count < 1)
                    consumerWait.WaitOne();

                edge.Dequeue(out token);
            }

            return 1;                       
        }

        /// <summary>
        /// Transfer data function, it performs the Dequeue() operation on connected edge 
        /// </summary>
        /// <param name="buffer">Buffer data</param>
        /// <param name="offset">Zero-based index offset</param>
        /// <param name="count">Number of tokens to be transfered</param>
        /// <returns>Number of tokens actually transfered</returns>
        public int Dequeue(T[] buffer, int offset, int count)
        {
            Debug.Assert(Type == PortType.I);
            lock (onlyOne)
            {
                consumerReq = count;
                if (edge.Count < count)
                    consumerWait.WaitOne();

                edge.Dequeue(buffer, offset, count);
            }
            return count;            
        }

        #region Properties
        /// <summary>
        /// queue's capacity
        /// </summary>
        public int Capacity
        {
            get
            {
                return edge.Capacity;
            }
        }

        /// <summary>
        /// Free space in the queue
        /// </summary>
        public int Space
        {
            get
            {
                return edge.Space;
            }
        }

        /// <summary>
        /// Number of tokens in the queue
        /// </summary>
        public int Count
        {
            get
            {
                return edge.Count;
            }
        }
        #endregion 

    }
}